//
//  PopSheet.m
//  PopSheet
//
//  Created by G2 on 2021/7/15.
//

#import "PopSheet.h"
#import "UIView+Support.h"
#import <objc/runtime.h>

void ps_swizzleInstanceMethod(Class cls, SEL originSelector, SEL swizzleSelector){
    if (!cls) {
        return;
    }
    /* if current class not exist selector, then get super*/
    Method originalMethod = class_getInstanceMethod(cls, originSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzleSelector);
    
    /* add selector if not exist, implement append with method */
    if (class_addMethod(cls,
                        originSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* replace class instance method, added if selector not exist */
        /* for class cluster , it always add new selector here */
        class_replaceMethod(cls,
                            swizzleSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(cls,
                            swizzleSelector,
                            class_replaceMethod(cls,
                                                originSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}

@interface PopSheetScrollRef :NSObject<UIScrollViewDelegate>

@property (nonatomic, weak) UIScrollView *scollView;
@property (nonatomic, weak) NSObject<UIScrollViewDelegate> *target;

- (instancetype)initWithRef:(id)target;

@end

@implementation PopSheetScrollRef

- (instancetype)initWithRef:(id)target{
    self = [super init];
    if (self) {
        self.target = target;
    }
    return self;
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
    if (self.scollView.hookedByPopSheet){
        *targetContentOffset = scrollView.contentOffset;
    }
    if ([self.target respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]){
        [self.target scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
    }
}
- (BOOL)respondsToSelector:(SEL)aSelector{
    BOOL isOk = [self.target respondsToSelector:aSelector];
    if (!isOk){
        isOk = [super respondsToSelector:aSelector];
    }
    return isOk;
}
-(void)forwardInvocation:(NSInvocation *)invocation{
    if([self.target respondsToSelector:invocation.selector]){
        [invocation invokeWithTarget:self.target];
    }else if ([self respondsToSelector:invocation.selector]){
        [invocation invokeWithTarget:self];
    }
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [super methodSignatureForSelector:sel] ?: [self.target methodSignatureForSelector:sel];
}

@end

@implementation UIScrollView(PopSheet)

+(void)load{
    ps_swizzleInstanceMethod([self class], @selector(setDelegate:), @selector(ps_setDelegate:));
}
- (void)ps_setDelegate:(id<UIScrollViewDelegate>)delegate{
    if (delegate == nil){
        [self ps_setDelegate:nil];
        return;
    }
    
    PopSheetScrollRef *ref =  [[PopSheetScrollRef alloc] initWithRef:delegate];
    ref.scollView = self;
    objc_setAssociatedObject(self, "ref", ref, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self ps_setDelegate:ref];
}

- (void)setHookedByPopSheet:(BOOL)hookedByPopSheet{
    objc_setAssociatedObject(self, "hookedByPopSheet", @(hookedByPopSheet), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (!self.delegate){
        self.delegate = self;
    }
}
- (BOOL)hookedByPopSheet{
    return [objc_getAssociatedObject(self, "hookedByPopSheet") boolValue];
}

@end

@interface PopSheet()

@property (nonatomic) UIView *contentView;
@property (nonatomic) CGFloat offset;
@property (nonatomic) BOOL enable;
@property (nonatomic) NSArray *subScrolls;
@property (nonatomic) CADisplayLink *displaylink;
@property (nonatomic) void (^block)(BOOL);

@end

@implementation PopSheet{
    CGFloat _offsetY;
}

- (void)dealloc
{
    for (UIScrollView *sc in self.subScrolls){
        [sc removeObserver:self forKeyPath:@"contentOffset"];
        for (UIGestureRecognizer *g in sc.gestureRecognizers){
            [g removeObserver:self forKeyPath:@"state"];
        }
        
    }
}

- (instancetype)init{
    self = [super init];
    if (self) {
        CGRect bounds = [UIScreen mainScreen].bounds;
        self.frame = CGRectMake(0,300, CGRectGetWidth(bounds), CGRectGetHeight(bounds)- 300);
        
        self.layer.shadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.3].CGColor;
        self.layer.shadowOpacity = 1;
        self.layer.shadowOffset = CGSizeMake(0, -2);
        self.layer.shadowRadius = 10;
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        _contentView = [[UIView alloc] init];
        [self addSubview:_contentView];
        
        _displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(display:)];
        if (@available(iOS 10.0, *)){
            _displaylink.preferredFramesPerSecond = 2;
        }else{
            _displaylink.frameInterval = 30;
        }
        [_displaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        [_displaylink setPaused:YES];
        
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
        [self addGestureRecognizer:pan];
    }
    return self;
}
- (void)setOffset:(CGFloat)offset{
    _offset = MIN(0, offset);
    self.transform = CGAffineTransformMakeTranslation(0, -_offset);
}

- (void)setContent:(UIView *)content{
    _content = content;
    [self.contentView addSubview:content];
    [self setNeedsLayout];
    [self display:nil];
}
- (void)display:(id)sender{
    if (!self.content) return;
    
    NSArray *array;
    [self findSubViewByCompare:^BOOL(UIView *obj) {
        return [obj isKindOfClass:[UIScrollView class]];
    } allSameType:YES container:&array];
    self.subScrolls = array;
    
    for (UIScrollView *sc in array){
        [sc addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
        for (UIGestureRecognizer *g in sc.gestureRecognizers){
            [g addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:nil];
        }
    }
}

- (void)layoutSubviews{
    [super layoutSubviews];
    self.contentView.frame = self.bounds;
    self.content.frame = self.contentView.bounds;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    UIScrollView *sc = object;
    if ([keyPath isEqualToString:@"contentOffset"]){
        [_displaylink setPaused:NO];
        
        @synchronized (self) {
            CGPoint offset = [change[NSKeyValueChangeNewKey] CGPointValue];
            
            if (!self.enable){
                if (offset.y < 0){
                    offset.y = 0;
                    [sc setContentOffset:offset];
                }
                return;
            }
            
            
            if (offset.y < 0){
                [sc setContentOffset:CGPointMake(offset.x, 0)];
                
                [UIView animateWithDuration:.1f animations:^{
                    self.offset += offset.y;
                }];
                
            }else if (offset.y > 0){
                if (self.offset < 0){
                    [UIView animateWithDuration:.1f animations:^{
                        self.offset += offset.y;
                    }];
                    [sc setContentOffset:CGPointMake(offset.x, 0)];
                }
            }
        }
        
    }else{
        UIGestureRecognizerState state = [change[NSKeyValueChangeNewKey] intValue];
        if (state == UIGestureRecognizerStateEnded
            || state == UIGestureRecognizerStateCancelled){
            @synchronized (self) {
                self.enable = NO;
            }
            [_displaylink setPaused:YES];
            if (self.offset >= 0) return;;
            
            if (fabs(self.offset) > CGRectGetHeight(self.bounds)/2){
                [NSObject cancelPreviousPerformRequestsWithTarget:self];
                [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1];
            }else{
                for (UIScrollView *sv in self.subScrolls) {
                    sv.hookedByPopSheet = YES;
                }
                [UIView animateWithDuration:.3f animations:^{
                    self.offset = 0;
                } completion:^(BOOL finished) {
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        for (UIScrollView *sv in self.subScrolls) {
                            sv.hookedByPopSheet = NO;
                        }
                    });
                }];
            }
        }else if (state == UIGestureRecognizerStateBegan){
            @synchronized (self) {
                self.enable = [object isKindOfClass:[UIPanGestureRecognizer class]];
                [_displaylink setPaused:NO];
                
            }
        }
    }
}

- (void)pan:(UIGestureRecognizer *)gesture{
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan:
        {
            self->_offsetY = [gesture locationInView:self.superview].y;
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            [UIView animateWithDuration:.1f animations:^{
                self.offset = self->_offsetY - [gesture locationInView:self.superview].y;
            }];
        }   break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        {
            if (self.offset >= 0) return;;
            
            if (fabs(self.offset) > CGRectGetHeight(self.bounds)/2){
                [NSObject cancelPreviousPerformRequestsWithTarget:self];
                [self performSelector:@selector(dismiss) withObject:nil afterDelay:0.1];
            }else{
                for (UIScrollView *sv in self.subScrolls) {
                    sv.hookedByPopSheet = YES;
                }
                [UIView animateWithDuration:.3f animations:^{
                    self.offset = 0;
                } completion:^(BOOL finished) {
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        for (UIScrollView *sv in self.subScrolls) {
                            sv.hookedByPopSheet = NO;
                        }
                    });
                }];
            }
        }
            break;
        default:
            break;
    }
}

- (void)showInView:(UIView *)superView block:(void (^)(BOOL))block{
    self.block = block;
    [superView addSubview:self];
    self.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(self.bounds));
    [UIView animateWithDuration:.3f animations:^{
        self.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        if (block){
            block(NO);
        }
    }];
}
- (void)dismiss{
    [UIView animateWithDuration:0.3f animations:^{
        self.offset = - CGRectGetHeight(self.bounds);
    } completion:^(BOOL finished) {
        [self removeFromSuperview];
        if (self.block){
            self.block(YES);
        }
    }];
}
@end
